import { useMemo, useState } from 'react';

import { useControllableValue } from 'ahooks';
import {
  Upload as AntdUpload,
  UploadFile as AntdUploadFile,
  UploadProps as AntdUploadProps
} from 'antd';
import { observer } from 'mobx-react-lite';

import { useStore } from '@/store';

import {
  filePlusAbortMultipartUpload,
  filePlusCompleteMultipartUpload,
  filePlusInitiateMultipartUpload,
  filePlusListParts,
  filePlusUploadPart
} from '@/services/base/kuozhangongnengwenjianguanliPlus';
import { formatFileSize, parseSrc } from '@/utils';
import { Icon } from '@iconify/react';

import { computeFileMD5, sliceFile } from './util';

type UploadFile = AntdUploadFile & {
  /** 是否暂停 */
  pause?: boolean;
  /** 是否取消 */
  canel?: boolean;
  /** 初始化接口 */
  init?: {
    response?: BASE.FileInfo;
    abortController?: AbortController;
  };
  /** 已上传分片 */
  uploaded?: {
    response?: BASE.FilePartInfo[];
    abortController?: AbortController;
  };
  /** 上传完成 */
  complete?: {
    response?: BASE.FileInfo;
    abortController?: AbortController;
  };
  /** 分片 */
  chunks?: {
    blob: Blob;
    md5: string;
    status: 'init' | 'uploading' | 'uploaded' | 'error';
    abortController: AbortController;
  }[];
};

/** 文件是否已存在 */
enum FileExists {
  '初始化完成' = 1,
  '已存在' = 2
}

// 视频后缀
export const VIDEO_SUFFIX = [
  'mp4',
  'flv',
  'avi',
  'wmv',
  'mov',
  'webm',
  'mpeg4',
  'ts',
  'mpg',
  'rm',
  'rmvb',
  'mkv',
  'm4v'
];
// 音频后缀
export const AUDIO_SUFFIX = ['mp3', 'wav'];
// 图片后缀
export const IMAGE_SUFFIX = ['jpg', 'jpeg', 'png', 'gif', 'webp'];

// 文件分隔符
const SEPARATOR = '?';

/** 上传接口返回值类型 */
export type FileResponse = BASE.FileInfo;

export type UploadSliceProps = Omit<AntdUploadProps<FileResponse>, 'onChange' | 'children'> & {
  value?: string;
  onChange?: (value?: string) => void;
  onAntdChange?: AntdUploadProps<FileResponse>['onChange'];
};

const UploadSlice = ({ onAntdChange, accept, ...rest }: UploadSliceProps) => {
  const store = useStore();
  const [value, setValue] = useControllableValue<UploadSliceProps['value']>(rest);
  const [fileListDict, setFileListDict] = useState<Record<string, UploadFile>>({});

  // 自定义上传
  const customRequest: UploadSliceProps['customRequest'] = async (options) => {
    try {
      const file = options.file as UploadFile;
      const originFile = options.file as File;
      // console.log(file.name)
      const fileMD5 = await computeFileMD5(originFile);

      if (!file.init) {
        file.init = {};
      }
      if (!file.uploaded) {
        file.uploaded = {};
      }
      if (!file.complete) {
        file.complete = {};
      }

      file.init.abortController = new AbortController();
      const init = (
        await filePlusInitiateMultipartUpload(
          {
            originalFilename: file.name,
            contentType: originFile.type,
            md5: fileMD5
          },
          {
            signal: file.init!.abortController.signal
          }
        )
      ).data;
      file.init.response = init;

      if (init?.uploadStatus === FileExists.已存在) {
        store.app?.message.info('文件秒传成功');
        options.onSuccess?.(init);

        return;
      }

      const chunks = await sliceFile(originFile);
      file.uploaded.abortController = new AbortController();
      // 服务器已上传的分片
      const uploadedChunks =
        (
          await filePlusListParts(
            { id: init!.id! },
            {
              signal: file.uploaded!.abortController.signal
            }
          )
        ).data || [];
      // 标记已上传的分片
      uploadedChunks.forEach((item) => {
        chunks[item.partNumber! - 1].status = 'uploaded';
      });
      file.uploaded.response = uploadedChunks;
      file.chunks = chunks;

      file.pause = false;
      file.canel = false;
      // 单次批量上传
      const batchCount = 10;
      const count = Math.ceil(chunks.length / batchCount);
      let currentBatchChunk = 0;

      while (currentBatchChunk < count) {
        if (file.pause || file.canel) {
          return;
        }

        const currentBatch = chunks.slice(
          currentBatchChunk * batchCount,
          (currentBatchChunk + 1) * batchCount
        );

        await Promise.all(
          currentBatch.map(async (chunk, index) => {
            if (chunk.status === 'uploaded') {
              return Promise.resolve();
            }

            const formData = new FormData();
            formData.append('initId', init!.id as string);
            formData.append('partNumber', String(currentBatchChunk * batchCount + index + 1));
            formData.append('file', chunk.blob);

            chunk.status = 'uploading';

            const [err, res] = await filePlusUploadPart(
              {} as ArgumentsType<typeof filePlusUploadPart>[0],
              {
                signal: chunk.abortController.signal,
                headers: {
                  'Content-Type': 'multipart/form-data'
                },
                data: formData,
                timeout: 60 * 10000,
                skipErrorHandler: true,
                tupleResponse: true
              }
            );

            if (err) {
              const error = new Error(err);
              options.onError?.(error);
              return Promise.reject(error);
            }

            chunk.status = !res ? 'error' : 'uploaded';

            const percent =
              (chunks.filter((v) => v.status === 'uploaded').length / chunks.length) * 100;
            options.onProgress?.({
              percent
            });
          })
        );

        if (!file.pause && !file.canel) {
          currentBatchChunk += 1;
        }
      }

      if (currentBatchChunk >= count) {
        file.complete.abortController = new AbortController();
        const fileInfo = (
          await filePlusCompleteMultipartUpload(
            {
              id: init!.id!
            },
            {
              signal: file.complete!.abortController.signal
            }
          )
        ).data;
        file.complete.response = fileInfo;

        options.onSuccess?.(fileInfo!);
      }
    } catch (e) {
      options.onError?.(e as Error);
    }
  };

  const fileList = useMemo(() => {
    const res: UploadFile[] = [];

    if (value) {
      value.split(SEPARATOR).forEach((url) => {
        if (fileListDict[url]) {
          res.push(fileListDict[url]);
        } else {
          res.push({
            uid: url,
            name: url,
            status: 'done',
            url: parseSrc(url),
            response: {
              url: url
            }
          });
        }
      });
    }

    return res;
  }, [fileListDict, value]);

  const onChange: AntdUploadProps<FileResponse>['onChange'] = (info) => {
    onAntdChange?.(info);
    setFileListDict((prev) => {
      return {
        ...prev,
        [info!.file.uid]: info.file
      };
    });

    const list = info.fileList.map((v) => v.response?.url || v.uid).join(SEPARATOR);
    setValue(list);
  };

  const onPause = (file: UploadFile) => {
    file.pause = true;
    file.chunks?.forEach((chunk) => {
      chunk.abortController.abort();
    });
  };

  const onContinue = (file: UploadFile) => {
    file.pause = false;

    // TODO
    // upload.run()
  };

  const onRemove: UploadSliceProps['onRemove'] = async (file) => {
    const originFileObj = file.originFileObj as UploadFile;
    if (originFileObj) {
      originFileObj.canel = true;
      originFileObj.init?.abortController?.abort();
      originFileObj.uploaded?.abortController?.abort();
      originFileObj.complete?.abortController?.abort();

      originFileObj.chunks?.forEach((chunk) => {
        chunk.abortController.abort();
      });

      if (originFileObj.init?.response) {
        await filePlusAbortMultipartUpload(
          {
            id: originFileObj.init.response.id
          },
          {
            tupleResponse: true
          }
        );
      }
    }
  };

  // const itemRender: UploadProps['itemRender'] = (originNode, file, fileList, actions) => {
  //   return React.cloneElement(originNode, {
  //     children: (
  //       <>
  //         {originNode.props.children}
  //         <div onClick={onPause.bind(null, file)}>暂停</div>
  //         <div onClick={onContinue.bind(null, file)}>继续上传</div>
  //       </>
  //     )
  //   })
  // }

  const defaultAccept = accept || VIDEO_SUFFIX.concat(AUDIO_SUFFIX, IMAGE_SUFFIX).join(',');

  return (
    <AntdUpload.Dragger
      accept={defaultAccept}
      multiple
      showUploadList
      listType="picture"
      // itemRender={itemRender}
      onRemove={onRemove}
      {...rest}
      customRequest={customRequest}
      fileList={fileList}
      onChange={onChange}
    >
      <p
        style={{
          fontSize: 36,
          lineHeight: 1.1,
          margin: 0,
          color: 'var(--ant-color-primary)'
        }}
      >
        <Icon icon="ri:upload-fill" />
      </p>
      <p className="ant-upload-text">点击或拖拽文件到此区域</p>
      <p className="ant-upload-hint">
        支持{defaultAccept}格式文件，单文件最大不超过{formatFileSize(2 * 1024 * 1024 * 1024)}
      </p>
    </AntdUpload.Dragger>
  );
};

export default observer(UploadSlice);
